"""
AI-MindCare, Multi-Persona-Agent System for Mental Health Support
Copyright (C) 2025  AI-MindCare Team
"""

####################
# 导入依赖库
####################
"""Python标准库"""
import os
import re
import io
import sys
import uuid
import time
import json
import random
import base64
from datetime import timezone, timedelta
from datetime import datetime, time as datetime_time  # 确保导入 datetime.time

import threading
from functools import wraps
import calendar

"""第三方库"""
import redis
import bcrypt
import logging
import urllib.request
from celery import Celery
from dotenv import load_dotenv
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import and_
from flask_cors import CORS
from flask import send_file
from flask_jwt_extended import get_jwt
from flask import Flask, render_template, request, jsonify, session as flask_session
from flask_jwt_extended import (
    create_access_token,
    create_refresh_token,
    jwt_required,
    get_jwt_identity,
    JWTManager,
)
from flask import Response, stream_with_context, request, jsonify
from werkzeug.security import generate_password_hash, check_password_hash


"""本地库"""
try:
    from agents.companion import CompanionAgent
    from utils.orchestrator import Orchestrator
    from utils.memory_system import MemorySystem
    from utils.llm_service import LLMService
    from config.settings import SETTINGS
    from config.config import Config
except ImportError as e:
    logging.critical(f"Web App 无法导入核心组件: {e}", exc_info=True)
    raise SystemExit(f"Web App 启动失败: {e}")
from models import (
    db,
    User,
    UserBuffer,
    Conversation,
    ConversationRecord,
    EmotionRecord,
    PsychologicalAssessmentRecord,
)
from utils.psychological_tests import (
    calculate_mbti,
    get_sds_depression_level,
    get_anxiety_level,
    get_baker_depression_level,
    get_baker_interpretation,
    get_sds_interpretation,
    get_sas_interpretation,
    MBTI_DESCRIPTIONS,
    REVERSE_QUESTIONS_SDS,
    REVERSE_QUESTIONS_SAS,
)
from core.tts import TTSClient


"""加载环境变量"""
load_dotenv()

# 设置时区为北京时间
import os

os.environ["TZ"] = "Asia/Shanghai"


try:
    import time
    time.tzset()
except AttributeError:
    # Windows系统不支持tzset
    pass

# -----------------------------------------------------------------------------------------------------------------------

# -----------------------------------------------------------------------------------------------------------------------
"""配置日志"""
logging.basicConfig(
    # filename="logs/mindcare_system_web.log",  # 新增日志文件路径
    level=logging.INFO,  # 可以根据 SETTINGS.debug_mode 调整
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[logging.StreamHandler(), logging.FileHandler("mindcare_system_web.log")],
    encoding="utf-8",  # 新增编码设置
)
# 创建日志记录器
logger = logging.getLogger("AI-MindCare-System-Web")
# 设置标准输出和标准错误的编码为UTF-8
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
sys.stdout = io.TextIOWrapper(
    sys.stdout.buffer, encoding="utf8"
)  # 改变标准输出的默认编码
# 清除原有可能使用错误编码的handler
for handler in logger.handlers[:]:
    logger.removeHandler(handler)
# 添加UTF-8编码的handler
handler = logging.StreamHandler(stream=sys.stdout)
handler.setFormatter(
    logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
)
handler.encoding = "utf-8"  # 明确指定编码为UTF-8
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# -----------------------------------------------------------------------------------------------------------------------

# -----------------------------------------------------------------------------------------------------------------------
"""初始化 Flask 实例"""
app = Flask(__name__)
CORS(app, supports_credentials=True)  # 支持跨域请求
app.config["SQLALCHEMY_DATABASE_URI"] = Config.database["SQLALCHEMY_DATABASE_URI"]
app.config["JWT_SECRET_KEY"] = Config.jwt["JWT_SECRET_KEY"]

app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=2)
jwt = JWTManager(app)
db.init_app(app)

# 配置 Flask session 密钥，用于加密会话数据# !!! 生产环境中必须使用强随机密钥，并从配置或环境变量读取 !!!
app.secret_key = SETTINGS.get(
    "FLASK_SECRET_KEY", os.urandom(24)
)  # SETTINGS是全局配置实例，供其他模块使用
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(hours=2)  # 设置会话超时时间
app.config["SESSION_COOKIE_HTTPONLY"] = True  # 防止会话cookie被JS读取
app.config["SESSION_COOKIE_SECURE"] = (
    not SETTINGS.debug_mode
)  # 强制HTTPS传输cookie（生产环境启用）
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"  # 防止CSRF
# -----------------------------------------------------------------------------------------------------------------------

# -----------------------------------------------------------------------------------------------------------------------
""" 配置redis """
redis_client = redis.Redis(
    host=Config.redis["REDIS_HOST"],
    port=Config.redis["REDIS_PORT"],
    password=Config.redis["REDIS_PASSWORD"],
    db=Config.redis["REDIS_DB"],
)
"""配置Celery (分布式任务队列系统，用于异步处理任务，如记忆更新等。)"""
redis_host = Config.redis["REDIS_HOST"]
redis_port = Config.redis["REDIS_PORT"]

celery = Celery("tasks", broker=f"redis://{redis_host}:{redis_port}/0")
"""配置限流"""
MAX_ATTEMPTS = 5
BLOCK_TIME = 300
# -----------------------------------------------------------------------------------------------------------------------

# -----------------------------------------------------------------------------------------------------------------------
# Token黑名单管理
jwt_blacklist = redis_client


@jwt.token_in_blocklist_loader
def check_if_token_in_blacklist(jwt_header, jwt_payload):
    jti = jwt_payload["jti"]
    return jwt_blacklist.exists(f"blacklist:{jti}")


# 会话不活跃监控
def start_inactivity_monitor():

    def monitor_loop():
        logger.info("启动会话不活跃监控线程")
        while True:
            try:
                logger.info("会话监控线程正在运行...")
                time.sleep(20)  # 每分钟检查一次
                current_time = datetime.now()

                # 获取所有活跃用户
                user_keys = redis_client.keys("user:active:*")
                for key in user_keys:
                    user_id = key.decode().split(":")[2]
                    last_active = datetime.fromisoformat(redis_client.get(key).decode())

                    # 如果用户超过30分钟不活跃，触发记忆更新
                    if (current_time - last_active).total_seconds() > 20:
                        logger.info("开始更新长期记忆")
                        update_long_term_memory.delay(user_id, "session_timeout")
                        redis_client.delete(key)  # 移除不活跃标记
            except Exception as e:
                logger.error(f"会话监控线程错误: {str(e)}", exc_info=True)
                time.sleep(60)  # 出错后等待1分钟再重试

    # 启动守护线程
    monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
    monitor_thread.start()


# 长期记忆更新任务
@celery.task
def update_long_term_memory(user_id, trigger_type="auto"):
    try:
        logger.info(f"开始更新用户{user_id}的长期记忆，触发类型：{trigger_type}")

        # 查询该用户在conversation_records表中的所有记录
        records = (
            db.session.query(ConversationRecord)
            .filter(ConversationRecord.user_id == user_id)
            .order_by(ConversationRecord.created_at.asc())
            .all()
        )  # 获取所有会话记录

        conversation_dict = {}
        for record in records:
            conversation_id = record.conversation_id
            if conversation_id not in conversation_dict:
                conversation_dict[conversation_id] = []
            record_dict = {
                "role": record.role,
                "content": record.content,
                "message_id": record.message_id,
                "parent_message_id": record.parent_message_id,
                "created_at": record.created_at.isoformat(),
            }
            conversation_dict[conversation_id].append(record_dict)

        prompt = f"""请根据以下历史会话生成一个全面的长期记忆，以 JSON 字典格式输出，内容需涵盖所有摘要的关键信息，例如来访者的性格、提出的核心困扰、咨询师采用的干预方法、达成的阶段性结论等。
    输出示例: {{"性格": "内向", "核心困扰": "工作压力大", "干预方法": "建议调整工作时间和心态", "阶段性结论": "来访者决定尝试调整"}}
    历史会话如下：{",".join(conversation_dict)}"""

        # 调用大模型生成长期记忆
        long_term_memory = mindcare_system["llm_service"].generate_response(
            prompt=prompt,
            system_message="你是一个专业的心理咨询长期记忆生成系统，需根据多个会话摘要生成逻辑清晰、全面准确的长期记忆，以 JSON 字典格式输出。",
            max_tokens=1000,
            temperature=0.5,
        )

        logger.info(
            f"成功为用户 {user_id} 生成长期记忆。长期记忆内容: {long_term_memory}"
        )

        # 更新users表中该用户的long_term_memory字段
        user = db.session.query(User).filter(User.id == user_id).first()
        if user:
            user.long_term_memory = long_term_memory
            db.session.commit()
        else:
            print(f"用户ID为{user_id}的用户未找到")
        db.session.close()
    except Exception as e:
        logger.error(f"更新用户{user_id}记忆失败：{str(e)}", exc_info=True)


# Celery Beat 配置 - 每日自动更新
celery.conf.beat_schedule = {
    "daily-memory-update": {
        "task": "app.update_long_term_memory",
        "schedule": timedelta(hours=24),
        "args": (
            None,
            "daily_update",
        ),  # None表示更新所有用户，实际实现中应遍历用户列表
    }
}

####################
# 系统初始化模块
####################
"""全局变量"""
mindcare_system = {}
conversation_personas = {}  # 用于存储每个会话的个性配置，如角色和个性

# 人设对应的默认音色映射
PERSONA_VOICE_MAP = {
    'professional': 'charles',      # 沉稳男声
    'therapist': 'claire',          # 温柔女声
    'youthful': 'david',            # 欢快男声
    'wise_elder': 'alex',           # 沉稳男声
    'artistic': 'bella',            # 激情女声
    'tech_rational': 'benjamin',    # 低沉男声
    'humorous': 'david',            # 欢快男声
    'nature_healer': 'claire',      # 温柔女声
    'life_coach': 'charles',        # 沉稳男声
    'animal_companion': 'diana',    # 欢快女声
    'spiritual_guide': 'anna',      # 沉稳女声
    'su_shi': 'alex',               # 沉稳男声
    'wu_ze_tian': 'anna',           # 沉稳女声
    'lu_xun': 'benjamin',           # 低沉男声
    'woolfe': 'claire',             # 温柔女声
    'mark_twain': 'david'           # 欢快男声
}
"""获取会话人设配置"""


def get_conversation_personas():
    personas_from_db = {}
    try:
        with app.app_context():  # 添加应用上下文
            conversations = Conversation.query.all()
            for conversation in conversations:
                personas_from_db[conversation.id] = conversation.persona
    except Exception as e:
        logger.error(f"从数据库加载会话个性配置失败: {str(e)}", exc_info=True)
    return personas_from_db


"""更新会话缓冲区"""


def update_conversation_and_buffer(
    user_id, conversation_id, role, content, parent_msg_id=None, model_metadata="{}"
):
    current_time = datetime.now(timezone.utc)
    try:
        # 更新 conversation_records 表
        new_conversation_record = ConversationRecord(
            user_id=user_id,
            conversation_id=conversation_id,
            role=role,
            content=content,
            message_id="",
            parent_message_id=parent_msg_id,
            created_at=current_time,
            model_metadata=model_metadata,
        )
        db.session.add(new_conversation_record)
        db.session.commit()

        message_id = f"msg_{new_conversation_record.id}"
        new_conversation_record.message_id = message_id
        db.session.commit()

        # 更新 user_buffers 表
        new_user_buffer = UserBuffer(
            user_id=user_id,
            conversation_id=conversation_id,
            role=role,
            content=content,
            message_id=message_id,
            parent_message_id=parent_msg_id,
            created_at=current_time,
            model_metadata=model_metadata,
        )
        db.session.add(new_user_buffer)
        db.session.commit()
        return message_id
    except Exception as e:
        db.session.rollback()
        print(f"更新 conversation_records 和 user_buffers 表时出错: {e}")
        return None


"""初始化系统组件"""


def init_mindcare_system():
    """初始化 AI-MindCare System 的所有组件"""
    global mindcare_system, user_buffers
    try:
        logger.info("开始初始化 AI-MindCare 核心系统...")
        # 确保 LLM 配置有效
        if not SETTINGS.validate_llm_config():  # 验证LLM配置是否有效
            logger.warning("LLM 配置不完整，功能可能受限。")
            # 可以选择是否继续，或者抛出错误
            # raise ValueError("LLM 配置无效，无法启动系统。")
        # 创建实例并初始化
        llm_service_instance = LLMService(settings=SETTINGS)  # 创建实例
        memory_system_instance = MemorySystem(llm_service=llm_service_instance)

        orchestrator_instance = Orchestrator(  # 编排器实例
            memory_system=memory_system_instance, llm_service=llm_service_instance
        )
        conversation_personas = get_conversation_personas()  # 初始化会话个性配置
        mindcare_system = {
            "orchestrator": orchestrator_instance,  # 编排器组件
            "memory_system": memory_system_instance,
            # "user_buffers": user_buffers,  # 用户会话缓冲区
            "conversation_personas": conversation_personas,  # 会话个性配置
            "llm_service": llm_service_instance,  # LLM 服务组件
        }
        logger.info("AI-MindCare System Successfully Initialized!")
        # start_inactivity_monitor()  # 启动会话监控流程 - 已禁用
        return True
    except Exception as e:
        logger.critical(
            f"Failed to initialize the AI-MindCare System: {str(e)}", exc_info=True
        )
        mindcare_system = {}  # 清空以表示失败
        return False


if (
    not init_mindcare_system()
):  # 在 Flask Web 应用正式启动前，预先检查核心系统的初始化状态，确保基础服务可用
    logger.critical("无法初始化核心系统，Web 应用可能无法正常工作。")
    # 可以选择退出应用或让其以受限模式运行
    # raise SystemExit("核心系统初始化失败，Web 应用无法启动。")

####################
# 会话模块
####################

"""文字转语音"""
from flask import send_file
from core.tts import TTSClient

tts_client = TTSClient()


@app.route("/api/tts", methods=["POST"])
def tts_api():
    """
    文本转语音接口
    请求体: {"text": "你好", "voice_source": "david"}
    返回: 音频流（mp3）
    男生音色：
    沉稳男声: alex
    低沉男声: benjamin
    磁性男声: charles
    欢快男声: david
    女生音色：
    沉稳女声: anna
    激情女声: bella
    温柔女声: claire
    欢快女声: dianac
    """
    data = request.get_json()
    text = data.get("text")
    voice_source = data.get("voice_source", "david")
    if not text:
        return jsonify({"code": 400, "msg": "缺少text参数"}), 400
    try:
        
        _, audio_bytes = tts_client.generate_speech(text, voice_source, save_path="./data/tt.mp3")
        audio_bytes.seek(0)
        return send_file(
            audio_bytes,
            mimetype="audio/mpeg",
            as_attachment=False,
            download_name="tts.mp3",
        )
    except Exception as e:
        return jsonify({"code": 500, "msg": f"语音合成失败: {str(e)}"}), 500

"""流式文本转语音接口"""
@app.route("/api/tts_stream", methods=["POST"])
def tts_api_stream():
    """流式文本转语音接口"""
    data = request.get_json()
    text = data.get("text")
    voice_source = data.get("voice_source", "dianac")
    
    if not text:
        return jsonify({"code": 400, "msg": "缺少text参数"}), 400
    
    try:
        # 创建TTS客户端实例
        tts_client = TTSClient()
        
        # 创建生成器函数
        def generate_audio():
            # 设置合适的块大小
            chunk_size = 4096  # 4KB块大小
            
            # 调用流式生成方法
            for chunk in tts_client.generate_speech_stream(
                text, 
                voice_source,
                chunk_size=chunk_size
            ):
                yield chunk
                
        # 返回流式响应
        return Response(
            generate_audio(),
            mimetype="audio/mpeg",
            headers={
                "Content-Type": "audio/mpeg",
                "Content-Disposition": "inline",
                "Cache-Control": "no-cache"
            }
        )
        
    except Exception as e:
        logger.error(f"语音合成失败: {str(e)}")
        return jsonify({"code": 500, "msg": f"语音合成失败: {str(e)}"}), 500

"""语音转文字"""
import base64
from core.stt import AudioTranscriber
@app.route("/api/stt", methods=["POST"])
def stt_api():
    """
    语音转文字接口
    请求体: {"audio_data": "base64_encoded_audio", "filename": "audio.wav", "content_type": "audio/wav"}
    返回: {"text": "转写后的文本"}
    """
    data = request.get_json()
    audio_data = data.get("audio_data")
    filename = data.get("filename", "audio.wav")
    content_type = data.get("content_type")

    if not audio_data:
        return jsonify({"code": 400, "msg": "缺少音频数据"}), 400

    try:
        # 解码 base64 音频数据
        audio_bytes = base64.b64decode(audio_data)
        audio_buffer = io.BytesIO(audio_bytes)
        
        audio_transcriber = AudioTranscriber()
        text = audio_transcriber.transcribe(audio_buffer, filename, content_type)
        
        # 检查是否是错误信息
        if isinstance(text, str) and text.startswith("请求失败"):
            return jsonify({"code": 500, "msg": text}), 500
            
        return jsonify({"text": text})
    except Exception as e:
        return jsonify({"code": 500, "msg": f"语音转文字失败: {str(e)}"}), 500



"""启动新的会话"""


@app.route("/api/start_conversation", methods=["POST"])
@jwt_required()  # 添加 JWT 验证装饰器，确保只有已登录用户才能启动咨询
def start_conversation():
    """开始新的会话（由前端调用）"""
    logger.info(f"开始新的 Web 会话，来自: {request.remote_addr}")
    logger.info(f"JWT 有效载荷: {get_jwt_identity()}")
    # 从 JWT 获取用户身份
    user_id = get_jwt_identity()

    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    redis_client.setex(f"user:active:{user_id}", 1800, datetime.now().isoformat())
    if not mindcare_system:
        return (
            jsonify({"status": "error", "message": "系统未初始化，请稍后重试。"}),
            503,
        )
    try:
        # 调用 Orchestrator 获取初始信息
        orchestrator = mindcare_system.get("orchestrator")  # 编排器
        if not orchestrator:
            return jsonify({"status": "error", "message": "编排器服务不可用。"}), 503
        # 启动新的咨询会话，获取 ID
        data = request.get_json()  # 获取AI人设信息
        persona = data.get("persona", "professional")
        conversation_id, initial_message = orchestrator.start_new_web_conversation(
            persona=persona
        )

        # 将 conversation_id 存储在 Flask session 中
        flask_session["conversation_id"] = conversation_id
        flask_session.permanent = True  # 使用配置的超时时间
        logger.info(
            f"为新 Web 会话 {request.remote_addr} 启动咨询，ID: {conversation_id}"
        )
        logger.info(f"生成的 conversation_id: {conversation_id}")

        new_conversation = Conversation(
            id=conversation_id,
            user_id=user_id,  # 使用 UUID 类型
            topic="",  # 可以根据需要设置标题
            start_time=datetime.now(timezone.utc),
            last_active_time=datetime.now(timezone.utc),
            persona=persona,  # 保存AI人设信息
            # model_metadata=json.dumps({
            #     "model_name": "deepseek-ai/DeepSeek-V3",
            #     "temperature": 0.5
            # }),
        )
        db.session.add(new_conversation)
        db.session.commit()

        update_conversation_and_buffer(
            user_id=user_id,
            conversation_id=new_conversation.id,
            role="ai",
            content=initial_message,
            parent_msg_id=None,
            model_metadata="{}",
        )

        conversation_personas[conversation_id] = persona  # 保存 AI 人设配置

        # 根据人设动态确定音色
        voice_source = PERSONA_VOICE_MAP.get(persona, "david")
        logger.info(f"启动会话使用的人设: {persona}, 音色: {voice_source}")

        # 生成TTS音频
        audio_base64 = ""
        try:
            logger.info(f"开始生成启动会话TTS音频，文本内容: {initial_message[:50]}...")
            audio_segment, audio_bytes = tts_client.generate_speech(
                initial_message, voice_source, save_path=None
            )

            # 将音频字节流转换为 Base64 编码
            audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
            audio_base64 = f"data:audio/mpeg;base64,{audio_base64}"
            logger.info(
                f"启动会话TTS音频生成成功，使用音色: {voice_source}，base64长度: {len(audio_base64)}"
            )
            
            # 将音频字节流转换为 Base64 编码
            audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
            audio_base64 = f"data:audio/mpeg;base64,{audio_base64}"
            logger.info(f"启动会话TTS音频生成成功，使用音色: {voice_source}，base64长度: {len(audio_base64)}")
        except Exception as e:
            logger.warning(f"启动会话TTS生成失败: {str(e)}")
            audio_base64 = ""

        return jsonify(
            {
                "status": "success",
                "conversation_id": conversation_id,
                "initial_message": {
                    "persona": persona,
                    "message": initial_message,
                    "audio_base64": audio_base64,
                },
            }
        )

    except Exception as e:
        logger.error(f"开始 Web 咨询失败: {str(e)}", exc_info=True)
        return (
            jsonify({"status": "error", "message": "开始咨询时发生服务器内部错误。"}),
            500,
        )


"""处理来自前端的对话消息"""
# 处理预检请求
@app.before_request
def handle_options():
    if request.method == "OPTIONS":
        response = jsonify({"status": "OK"})
        response.headers["Access-Control-Allow-Origin"] = "*"
        response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
        response.headers["Access-Control-Allow-Headers"] = "*"
        return response, 200




@app.route("/api/conversation", methods=["POST"])
@jwt_required()  # 新增JWT验证
def handle_conversation():
    """处理来自前端的对话消息"""

    user_id = get_jwt_identity()

    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    redis_client.setex(f"user:active:{user_id}", 1800, datetime.now().isoformat())
    if not mindcare_system:
        return (
            jsonify({"status": "error", "message": "系统未初始化，请稍后重试。"}),
            503,
        )

    try:
        try:
            data = request.get_json(
                force=True, silent=False
            )  # 强制解析 JSON 数据（即使没有 Content-Type: application/json）
            if not data:  # 检查数据是否为空
                return jsonify({"status": "error", "message": "无效的JSON格式"}), 400
        except Exception as e:  # 捕获解析错误
            logger.error(f"解析请求数据失败: {str(e)}", exc_info=True)
            return (
                jsonify({"status": "error", "message": f"解析请求数据失败: {str(e)}"}),
                400,
            )

        conversation_id = flask_session.get("conversation_id")
        if (
            not conversation_id
        ):  # 如果 session 中没有，尝试从请求中获取（作为后备或调试）
            conversation_id = data.get("conversation_id")
            logger.warning(
                f"无法从 session 获取 conversation_id，尝试从请求体获取: {conversation_id}"
            )
        if not conversation_id:
            return (
                jsonify(
                    {
                        "status": "error",
                        "message": "会话ID丢失，请刷新页面开始新的咨询。",
                    }
                ),
                400,
            )

        user_message = data.get("message")
        logger.info(f"用户消息：{user_message}")

        # 获取图片数据（无论是否有文本消息）
        image_base64 = data.get("image_base64", "")
        logger.info(f"图片数据: {image_base64}")

        logger.info(f"Web 消息接收: {user_message}")  # 记录消息内容，用于调试和日志记录
        if not user_message:
            user_audio = data.get("audio")
            logger.info(f"音频数据: {user_audio}")
            
            # 检查是否有音频或图片
            if not user_audio and not image_base64:
                return jsonify({"status": "error", "message": "发送的消息不能为空。"}), 400
            
            # 如果有音频，进行语音转文字
            if user_audio:
                filename = data.get("filename", "audio.wav")
                content_type = data.get("content_type")
                # 解码 base64 音频数据
                audio_bytes = base64.b64decode(user_audio)
                audio_buffer = io.BytesIO(audio_bytes)
                
                audio_transcriber = AudioTranscriber()
                user_message = audio_transcriber.transcribe(audio_buffer, filename, content_type)
            else:
                # 如果是纯图片，设置默认消息
                user_message = "图片消息"
        



        # 图片数据已经在上面获取过了，这里不需要重复获取
        model_key = data.get("model_key")
        
        
        last_message = (
            ConversationRecord.query.filter_by(conversation_id=conversation_id)
            .order_by(ConversationRecord.created_at.desc())
            .first()
        )
        
        persona = conversation_personas.get(
            conversation_id, "professional"
        )  # 获取会话的个性配置
        long_term_memory = flask_session.get("long_term_memory", "")  # 获取长期记忆
        # 获取用户的会话缓冲区
        user_buffer = mindcare_system.get("memory_system").get_user_buffer(
            conversation_id
        )

        parent_msg_id = last_message.message_id if last_message else None
        # 先更新数据库

        message_id = update_conversation_and_buffer(
            user_id=user_id,
            conversation_id=conversation_id,
            role="user",
            content=user_message,
            parent_msg_id=parent_msg_id,
            model_metadata="{}",
        )

        if (
            len(user_buffer) >= 2
            and mindcare_system["memory_system"].get_topic(conversation_id) == ""
        ):
            mindcare_system["memory_system"].generate_topic_for_conversation(
                conversation_id
            )

        orchestrator = mindcare_system.get("orchestrator")
        if not orchestrator:
            return jsonify({"status": "error", "message": "编排器服务不可用。"}), 503
        
        
        response_data = orchestrator.handle_web_message(
            conversation_id=conversation_id,
            user_message=user_message,
            user_buffer=user_buffer,
            long_term_memory=long_term_memory,
            persona=persona,
            model_key=model_key,
            image_base64 = image_base64,
        )
        

        model_metadata = {"model_name": model_key}
        # 如果 Orchestrator 返回错误状态，直接返回
        if response_data.get("status") == "error":
            return jsonify(response_data), 500  # 或根据错误类型返回不同状态码
        logger.info(f"返回的数据{response_data}")

        next_response = response_data.get("response")
        if not next_response:
            logger.error("AI回复为空，无法插入数据库。")
            return (
                jsonify({"status": "error", "message": "AI回复为空，请稍后重试。"}),
                500,
            )
        model_metadata["temperature"] = response_data.get("temperature")
        model_metadata = json.dumps(model_metadata)


        # 根据人设动态确定音色
        voice_source = PERSONA_VOICE_MAP.get(persona, "david")
        logger.info(f"当前会话 {conversation_id} 使用的人设: {persona}, 音色: {voice_source}")

        # 生成TTS音频
        try:
            logger.info(f"开始生成TTS音频，文本内容: {next_response[:50]}...")
            audio_segment, audio_bytes = tts_client.generate_speech(
                next_response, voice_source, save_path=None
            )
            
            # 将音频字节流转换为 Base64 编码
            audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
            response_data["audio_base64"] = f"data:audio/mpeg;base64,{audio_base64}"
            logger.info(f"TTS音频生成成功，使用音色: {voice_source}，base64长度: {len(audio_base64)}")
        except Exception as e:
            logger.warning(f"TTS生成失败: {str(e)}")
            response_data["audio_base64"] = ""
        # 再次根据AI返回的内容更新会话缓冲区
        current_time = datetime.now(timezone.utc)
        update_conversation_and_buffer(
            user_id=user_id,
            conversation_id=conversation_id,
            role="ai",
            content=next_response,
            parent_msg_id=message_id,
            model_metadata=model_metadata,
        )
        return jsonify(response_data)

    except Exception as e:
        logger.error(
            f"处理 Web 对话失败 (Session Consult ID: {flask_session.get('conversation_id')}): {str(e)}",
            exc_info=True,
        )
        return (
            jsonify(
                {"status": "error", "message": "处理您的消息时发生服务器内部错误。"}
            ),
            500,
        )




    ###############################################

    # """
    # 流式调用硅基流动API，返回SSE格式响应
    # """
    # # 从URL参数获取prompt
    # # prompt = request.args.get("prompt", "")
    # # if not prompt:
    # #     return Response(
    # #         response=json.dumps({"error": "缺少prompt参数"}),
    # #         status=400,
    # #         mimetype="application/json",
    # #     )
    # history_messages = []
    # for item in user_buffer:
    #     role = "assistant" if item["role"] == "ai" else "user"
    #     history_messages.append({"role": role, "content": item["content"]})

    # # 构建包含历史的messages
    # llm_payload = {
    #     "model": 'deepseek-ai/DeepSeek-V3',
    #     "messages": history_messages + [{"role": "user", "content": user_message}],  # 拼接历史+当前消息
    #     "stream": True,
    # }
    # headers = {
    #     "Authorization": f"Bearer sk-svuwiezizbxyflaqnhapzgijjdmeiaotknizeezdwzesoruw",
    #     "Content-Type": "application/json",
    # }
    # import requests
    
    # full_text = ""  # 用于收集完整文本
    # def generate():
    #     nonlocal full_text
    #     try:
    #         # 2. 调用LLM的流式接口（如硅基流动）
    #         resp = requests.post(
    #             "https://api.siliconflow.cn/v1/chat/completions",
    #             headers=headers,
    #             json=llm_payload,
    #             stream=True,
    #             timeout=60,
    #         )
    #         resp.raise_for_status()
            
    #         # 3. 迭代处理LLM的流式响应
    #         for line in resp.iter_lines():
    #             if not line:
    #                 continue
    #             line_str = line.decode("utf-8").strip()
                
    #             # 4. 解析文本片段（根据LLM接口格式调整）
    #             if line_str.startswith("data: "):
    #                 line_str = line_str[6:]  # 去掉"data: "前缀
    #             if line_str == "[DONE]":
    #                 break  # 停止迭代LLM响应
                
    #             # 5. 提取文本内容并拼接
    #             chunk = json.loads(line_str)
    #             text_chunk = chunk["choices"][0]["delta"].get("content", "")
    #             if text_chunk:
    #                 full_text += text_chunk  # 收集完整文本
    #                 yield f"data: {json.dumps({'text': text_chunk})}\n\n"  # 返回文本片段
            
    #         # 6. 流式传输完成→生成语音
    #         if full_text:
    #             # 7. 调用TTS生成语音（保存到临时文件）
    #             voice_path = f"temp_voice_{conversation_id}_{message_id}.mp3"
    #             tts_client.generate_speech(full_text, PERSONA_VOICE_MAP.get(persona, "david"), save_path=voice_path)
                
    #             # 8. 生成语音访问URL（后端需配置静态文件访问）
    #             voice_url = url_for("static", filename=voice_path, _external=True)
                
    #             # 9. 返回语音就绪信号（包含URL）
    #             yield f"data: {json.dumps({'voice_ready': True, 'voice_url': voice_url})}\n\n"
            
    #         # 10. 返回结束信号
    #         yield "data: [DONE]\n\n"
        
    #     except Exception as e:
    #         logger.error(f"对话处理失败: {str(e)}")
    #         yield f"data: {json.dumps({'error': str(e)})}\n\n"
    
    # # 11. 返回流式响应
    # return Response(
    #     stream_with_context(generate()),
    #     mimetype="text/event-stream",
    #     headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
    # )

@app.route("/api/conversation_stream", methods=["POST"])
@jwt_required()  # 新增JWT验证
def handle_conversation_stream():
    """处理来自前端的对话消息"""

    user_id = get_jwt_identity()

    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    redis_client.setex(f"user:active:{user_id}", 1800, datetime.now().isoformat())
    if not mindcare_system:
        return (
            jsonify({"status": "error", "message": "系统未初始化，请稍后重试。"}),
            503,
        )

    try:
        try:
            data = request.get_json(
                force=True, silent=False
            )  # 强制解析 JSON 数据（即使没有 Content-Type: application/json）
            if not data:  # 检查数据是否为空
                return jsonify({"status": "error", "message": "无效的JSON格式"}), 400
        except Exception as e:  # 捕获解析错误
            logger.error(f"解析请求数据失败: {str(e)}", exc_info=True)
            return (
                jsonify({"status": "error", "message": f"解析请求数据失败: {str(e)}"}),
                400,
            )

        conversation_id = flask_session.get("conversation_id")
        if (
            not conversation_id
        ):  # 如果 session 中没有，尝试从请求中获取（作为后备或调试）
            conversation_id = data.get("conversation_id")
            logger.warning(
                f"无法从 session 获取 conversation_id，尝试从请求体获取: {conversation_id}"
            )
        if not conversation_id:
            return (
                jsonify(
                    {
                        "status": "error",
                        "message": "会话ID丢失，请刷新页面开始新的咨询。",
                    }
                ),
                400,
            )

        user_message = data.get("message")
        logger.info(f"用户消息：{user_message}")

        # 获取图片数据（无论是否有文本消息）
        image_base64 = data.get("image_base64", "")
        logger.info(f"图片数据: {image_base64}")

        logger.info(f"Web 消息接收: {user_message}")  # 记录消息内容，用于调试和日志记录
        if not user_message:
            user_audio = data.get("audio")
            logger.info(f"音频数据: {user_audio}")
            
            # 检查是否有音频或图片
            if not user_audio and not image_base64:
                return jsonify({"status": "error", "message": "发送的消息不能为空。"}), 400
            
            # 如果有音频，进行语音转文字
            if user_audio:
                filename = data.get("filename", "audio.wav")
                content_type = data.get("content_type")
                # 解码 base64 音频数据
                audio_bytes = base64.b64decode(user_audio)
                audio_buffer = io.BytesIO(audio_bytes)
                
                audio_transcriber = AudioTranscriber()
                user_message = audio_transcriber.transcribe(audio_buffer, filename, content_type)
            else:
                # 如果是纯图片，设置默认消息
                user_message = "图片消息"
        



        # 图片数据已经在上面获取过了，这里不需要重复获取
        model_key = data.get("model_key")
        
        
        last_message = (
            ConversationRecord.query.filter_by(conversation_id=conversation_id)
            .order_by(ConversationRecord.created_at.desc())
            .first()
        )
        
        persona = conversation_personas.get(
            conversation_id, "professional"
        )  # 获取会话的个性配置
        long_term_memory = flask_session.get("long_term_memory", "")  # 获取长期记忆
        # 获取用户的会话缓冲区
        user_buffer = mindcare_system.get("memory_system").get_user_buffer(
            conversation_id
        )

        parent_msg_id = last_message.message_id if last_message else None
        # 先更新数据库

        message_id = update_conversation_and_buffer(
            user_id=user_id,
            conversation_id=conversation_id,
            role="user",
            content=user_message,
            parent_msg_id=parent_msg_id,
            model_metadata="{}",
        )

        if (
            len(user_buffer) >= 2
            and mindcare_system["memory_system"].get_topic(conversation_id) == ""
        ):
            mindcare_system["memory_system"].generate_topic_for_conversation(
                conversation_id
            )

        orchestrator = mindcare_system.get("orchestrator")
        if not orchestrator:
            return jsonify({"status": "error", "message": "编排器服务不可用。"}), 503
        
        def generate():
            full_content = ""
            for response_data in orchestrator.handle_web_message_stream(
                conversation_id=conversation_id,
                user_message=user_message,
                user_buffer=user_buffer,
                long_term_memory=long_term_memory,
                persona=persona,
                model_key=model_key,
                image_base64 = image_base64,
            ):
        

                model_metadata = {"model_name": model_key}
                # 如果 Orchestrator 返回错误状态，直接返回
                if response_data.get("status") == "error":
                    yield f"data: {json.dumps(response_data)}\n\n"
                    # yield jsonify(response_data), 500  # 或根据错误类型返回不同状态码
                logger.info(f"返回的数据{response_data}")

                next_response = response_data.get("response")
                # if not next_response:
                #     logger.error("AI回复为空，无法插入数据库。")
                #     yield (
                #         jsonify({"status": "error", "message": "AI回复为空，请稍后重试。"}),
                #         500,
                #     )
                full_content += next_response
                model_metadata["temperature"] = response_data.get("temperature")
                model_metadata = json.dumps(model_metadata)
                # yield jsonify(response_data)
                yield f"data: {json.dumps(response_data)}\n\n"

            # # 根据人设动态确定音色
            # voice_source = PERSONA_VOICE_MAP.get(persona, "david")
            # logger.info(f"当前会话 {conversation_id} 使用的人设: {persona}, 音色: {voice_source}")

            # # 生成TTS音频
            # try:
            #     logger.info(f"开始生成TTS音频，文本内容: {full_content[:50]}...")
            #     audio_segment, audio_bytes = tts_client.generate_speech(
            #         full_content, voice_source, save_path=None
            #     )
                
            #     # 将音频字节流转换为 Base64 编码
            #     audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
            #     response_data["audio_base64"] = f"data:audio/mpeg;base64,{audio_base64}"
            #     logger.info(f"TTS音频生成成功，使用音色: {voice_source}，base64长度: {len(audio_base64)}")
            # except Exception as e:
            #     logger.warning(f"TTS生成失败: {str(e)}")
            #     response_data["audio_base64"] = ""
            # # 再次根据AI返回的内容更新会话缓冲区
            # current_time = datetime.now(timezone.utc)
            # update_conversation_and_buffer(
            #     user_id=user_id,
            #     conversation_id=conversation_id,
            #     role="ai",
            #     content=next_response,
            #     parent_msg_id=message_id,
            #     model_metadata=model_metadata,
            # )
            # yield jsonify(response_data)
        
        return Response(stream_with_context(generate()), mimetype="text/event-stream")

    except Exception as e:
        logger.error(
            f"处理 Web 对话失败 (Session Consult ID: {flask_session.get('conversation_id')}): {str(e)}",
            exc_info=True,
        )
        return (
            jsonify(
                {"status": "error", "message": "处理您的消息时发生服务器内部错误。"}
            ),
            500,
        )
     




####################
# 登录模块
####################
"""渲染主页"""


@app.route("/")
def index():
    return render_template("index.html")




"""获取历史会话记录分页"""


@app.route("/api/conversation/history", methods=["GET"])
@jwt_required()
def get_conversation_history():
    user_id = get_jwt_identity()
    page = request.args.get("page", 1, type=int)
    per_page = request.args.get("per_page", 10, type=int)  # 从前端获取per_page参数

    pagination = (
        Conversation.query.filter_by(user_id=user_id)
        .order_by(Conversation.last_active_time.desc())
        .paginate(page=page, per_page=per_page, error_out=False)
    )

    conv_list = []
    for conv in pagination.items:
        conv_list.append(
            {
                "conversation_id": conv.id,
                "topic": conv.topic,
                "persona": conv.persona,
                "start_time": conv.start_time.isoformat() if conv.start_time else "",
                "last_active_time": (
                    conv.last_active_time.isoformat() if conv.last_active_time else ""
                ),
            }
        )

    return jsonify(
        code=200,
        data={
            "conversations": conv_list,
            "total": pagination.total,
            "page": page,
            "per_page": per_page,
        },
    )


"""获取会话记录详情"""


@app.route("/api/conversation/<conversation_id>", methods=["GET"])
@jwt_required()
def get_conversation_detail(conversation_id):
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    records = (
        ConversationRecord.query.filter_by(
            conversation_id=conversation_id, user_id=user_id
        )
        .order_by(ConversationRecord.created_at.asc())
        .all()
    )

    if not records:
        return jsonify(code=404, msg="记录不存在"), 404

    messages = []
    for r in records:
        messages.append(
            {
                "role": r.role,
                "content": r.content,
                "created_at": r.created_at.isoformat() if r.created_at else "",
                "message_id": r.message_id,
                "parent_message_id": r.parent_message_id,
                "model_metadata": r.model_metadata,
            }
        )

    return jsonify(code=200, data={"messages": messages})


####################
# 登录模块
####################
"""渲染主页"""



""""自定义限流装饰器"""


def rate_limit(limit_key_prefix, limit=1, period=60):
    """
    自定义限流装饰器
    :param limit_key_prefix: Redis键前缀
    :param limit: 允许的请求次数
    :param period: 限制周期（秒）
    """

    def decorator(func):

        @wraps(func)
        def wrapper(*args, **kwargs):
            # 获取用户标识（这里使用手机号或邮箱）
            identifier = request.json.get("phone") or request.json.get("email")
            if not identifier:
                return jsonify({"code": 400, "msg": "缺少必要参数"}), 400

            # 限流键
            limit_key = f"{limit_key_prefix}:{identifier}"

            # 使用Redis的INCR和EXPIRE原子操作实现限流
            with redis_client.pipeline() as pipe:
                try:
                    pipe.incr(limit_key)
                    pipe.expire(limit_key, period)
                    current, _ = pipe.execute()

                    if current > limit:
                        # 获取剩余时间（秒）
                        ttl = redis_client.ttl(limit_key)
                        return (
                            jsonify(
                                {"code": 429, "msg": f"操作过于频繁，请{ttl}秒后再试"}
                            ),
                            429,
                        )

                    return func(*args, **kwargs)
                except Exception as e:
                    logger.error(f"限流检查失败: {str(e)}", exc_info=True)
                    # 发生错误时，默认允许请求通过
                    return func(*args, **kwargs)

        return wrapper

    return decorator


"""发送手机验证码"""


@celery.task
def async_send_sms(phone, code):
    try:
        logger.info(f"模拟发送短信至: {phone}, 验证码: {code}")
        # send_sms(phone, code)
        # pass
    except Exception as e:
        logger.error(f"手机验证码发送失败: {str(e)}", exc_info=True)


@app.route("/api/send_sms", methods=["POST"])
@rate_limit("sms_rate", limit=3, period=60)  # 1分钟内最多3次请求
def handle_send_sms():
    phone = request.json.get("phone")

    if not phone:
        return jsonify({"code": 400, "msg": "缺少手机号参数"}), 400

    # 校验手机号格式（国内）
    if not re.match(r"^1[3-9]\d{9}$", phone):
        return jsonify({"code": 400, "msg": "手机号格式不正确"}), 400

    # 检查是否已注册（注册时需校验）
    if request.path == "/register" and User.query.filter_by(phone=phone).first():
        return jsonify({"code": 400, "msg": "该手机号已被注册"}), 400

    try:
        # 生成随机验证码
        # code = str(random.randint(100000, 999999))
        code = "666666"  # 先写死，便于测试

        # 异步发送短信
        async_send_sms.delay(phone, code)

        # 存储验证码到Redis，设置5分钟有效期
        redis_client.setex(f"sms_code:{phone}", 300, code)

        # 返回脱敏手机号
        masked_phone = phone[:3] + "****" + phone[7:]

        return jsonify(
            {
                "code": 200,
                "msg": f"验证码已发送至手机: {masked_phone}",
                "data": {"masked_phone": masked_phone},
            }
        )

    except Exception as e:
        logger.error(f"发送验证码失败: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": "服务器内部错误，请稍后再试"}), 500


"""发送邮箱验证码"""


@celery.task
def async_send_email(email, code):  # 修正参数名
    try:
        logger.info(f"发送邮件至: {email}, 验证码: {code}")
        # 实际邮件发送逻辑
        # send_email(email, "验证码", f"您的验证码是: {code}")
    except Exception as e:
        logger.error(f"邮件发送失败: {str(e)}", exc_info=True)


@app.route("/api/send_email", methods=["POST"])
@rate_limit("email_rate", limit=3, period=60)  # 1分钟内最多3次请求
def handle_send_email():
    email = request.json.get("email")

    if not email:
        return jsonify({"code": 400, "msg": "缺少邮箱参数"}), 400

    # 校验邮箱格式
    if not re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
        return jsonify({"code": 400, "msg": "邮箱格式不正确"}), 400

    # 检查是否已注册（仅注册接口需要）
    if request.path == "/register" and User.query.filter_by(email=email).first():
        return jsonify({"code": 400, "msg": "该邮箱已被注册"}), 400

    try:
        # 生成随机验证码
        code = str(random.randint(100000, 999999))
        code = "888888"

        # 异步发送邮件
        async_send_email.delay(email, code)

        # 存储验证码到Redis
        pipe = redis_client.pipeline()
        pipe.setex(f"email_code:{email}", 300, code)  # 验证码有效期5分钟
        pipe.setex(f"email_sent:{email}", 60, "1")  # 防止重复发送
        pipe.execute()

        # 返回脱敏后的邮箱
        masked_email = email.replace(
            email[email.index("@") - 3 : email.index("@")], "***"
        )

        return jsonify(
            {
                "code": 200,
                "msg": f"验证码已发送至邮箱: {masked_email}",
                "data": {"masked_email": masked_email},
            }
        )

    except Exception as e:
        logger.error(f"发送邮箱验证码失败: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": "服务器内部错误，请稍后再试"}), 500


"""手机验证码登录"""


@app.route("/api/login_with_phone", methods=["POST"])
def login_with_phone():
    phone = request.json.get("phone")  # 获取手机号
    code = request.json.get("code")  # 获取验证码
    # 校验验证码
    stored_code = redis_client.get(f"sms_code:{phone}")
    logger.info(
        "手机号{} 验证码对比: stored_code={}, input_code={}".format(
            phone, stored_code, code
        )
    )  # str.format()
    if not stored_code:
        # 若 stored_code 为 None，可能是验证码过期或未存储
        return jsonify(code=400, msg="验证码已过期或未发送，请重新获取"), 400
    if not stored_code or stored_code.decode() != code:
        return jsonify(code=400, msg="验证码错误"), 400

    # 查询用户是否存在（不存在则自动注册）
    user = User.query.filter_by(phone=phone).first()

    if not user:
        user = User(phone=phone)
        user.long_term_memory = ""  # 初始化长期记忆为空字符串
        db.session.add(user)
        db.session.commit()

    long_term_memory = user.long_term_memory  # 加载长期记忆
    flask_session["long_term_memory"] = long_term_memory  # 将长期记忆存储在会话中

    # 生成JWT令牌
    access_token = create_access_token(identity=user.id)
    refresh_token: str = create_refresh_token(identity=user.id)

    # 清除已使用的验证码
    redis_client.delete(f"sms_code:{phone}")

    return jsonify(
        code=200,
        msg="登录成功",
        data={"access_token": access_token, "refresh_token": refresh_token},
    )


"""邮箱验证码登录"""


@app.route("/api/login_with_email", methods=["POST"])
def login_with_email():
    email = request.json.get("email")  # 获取邮箱
    code = request.json.get("code")  # 获取验证码
    if not re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
        return jsonify(code=400, msg="错误的邮箱格式"), 400

    stored_code = redis_client.get(f"email_code:{email}")
    logger.info(
        "邮箱{} 验证码对比: stored_code={}, input_code={}".format(
            email, stored_code, code
        )
    )

    if not stored_code:
        return jsonify(code=400, msg="验证码已过期或未发送，请重新获取"), 400
    if not stored_code or stored_code.decode() != code:
        return jsonify(code=400, msg="验证码错误"), 400

    user = User.query.filter_by(email=email).first()
    if not user:
        user = User(email=email)
        use.long_term_memory = ""
        db.session.add(user)
        db.session.commit()

    long_term_memory = user.long_term_memory  # 加载长期记忆
    flask_session["long_term_memory"] = long_term_memory  # 将长期记忆存储在session中

    access_token = create_access_token(identity=user.id)
    refresh_token = create_refresh_token(identity=user.id)
    redis_client.delete(f"email_code:{email}")
    return jsonify(
        code=200,
        msg="登录成功",
        data={"access_token": access_token, "refresh_token": refresh_token},
    )


"""设置密码"""


@app.route("/api/register/set_password", methods=["POST"])
@jwt_required()  # 假设注册流程已通过JWT临时令牌验证
def set_password():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页

    password = request.json.get("password")
    confirm_password = request.json.get("confirm_password")

    logger.info(
        "密码对比: password={}, confirm_password={}".format(password, confirm_password)
    )  # str.format()

    print(user_id)
    # 1. 校验两次密码是否一致
    if password != confirm_password:
        return jsonify(code=400, msg="两次输入密码不一致"), 400
    # logger.info("用户id{} 密码对比: ={}, confirm_password={}".format(user_id, password, confirm_password))

    # 2. 密码复杂度校验（示例：至少8位，包含字母和数字）
    if not re.match(r"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$", password):
        return jsonify(code=400, msg="密码需至少8位且包含字母和数字"), 400

    # 3. 生成密码哈希并存储
    user = User.query.get(user_id)
    logger.info(
        "用户id{} 密码哈希: password_hash={}".format(user_id, user.password_hash)
    )  # str.format()
    # user.password_hash = bcrypt.generate_password_hash(password).decode("utf-8")
    user.password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode(
        "utf-8"
    )
    db.session.commit()
    return jsonify(code=200, msg="密码设置成功")


"""账户密码登录"""


@app.route("/api/login/password", methods=["POST"])
def login_with_password():
    # 统一使用identifier接收账号（手机号或邮箱）
    identifier = request.json.get("identifier")
    password = request.json.get("password")

    if not identifier or not password:
        return jsonify(code=400, msg="缺少账号或密码"), 400

    # 统一限流键（使用identifier替代phone）
    block_key = f"login_block:{identifier}"
    if redis_client.get(block_key):
        return jsonify(code=429, msg="尝试次数过多，请5分钟后再试"), 429

    # 优先按手机号查询，不存在则按邮箱查询
    user = User.query.filter(
        (User.phone == identifier) | (User.email == identifier)
    ).first()

    if not user:
        return jsonify(code=400, msg="账号未注册"), 400

    # 密码验证
    if not bcrypt.checkpw(password.encode(), user.password_hash.encode()):
        attempt_key = f"login_attempts:{identifier}"
        attempts = redis_client.incr(attempt_key)
        redis_client.expire(attempt_key, BLOCK_TIME)  # BLOCK_TIME需定义（如300秒）

        if attempts >= MAX_ATTEMPTS:  # MAX_ATTEMPTS需定义（如5次）
            redis_client.setex(block_key, BLOCK_TIME, "1")

        return jsonify(code=400, msg="密码错误"), 400

    # 登录成功逻辑
    flask_session["long_term_memory"] = user.long_term_memory
    access_token = create_access_token(identity=user.id)
    refresh_token = create_refresh_token(identity=user.id)

    return jsonify(
        code=200,
        msg="登录成功",
        data={
            "access_token": access_token,
            "refresh_token": refresh_token,
            "user_type": "phone" if user.phone == identifier else "email",
        },
    )


"""查看Session"""


@app.route("/debug/session")
def debug_session():
    # 将 flask_session 中的内容转换为字典后返回
    return jsonify(dict(flask_session))


"""退出登录"""


@app.route("/api/logout", methods=["POST"])
@jwt_required()
def logout():
    try:
        # 新增：触发长期记忆更新
        jti = get_jwt()["jti"]
        user_id = get_jwt_identity()

        # 获取 JWT 过期时间（timedelta 对象）
        token_expires = app.config["JWT_ACCESS_TOKEN_EXPIRES"]
        # 转换为整数秒数
        expires_seconds = int(token_expires.total_seconds())

        # 将 Token 加入黑名单（确保时间参数为整数）
        jwt_blacklist.setex(f"blacklist:{jti}", expires_seconds, "true")
        logger.info(
            f"用户 {user_id} 的Token {jti} 已加入黑名单，过期时间：{expires_seconds} 秒"
        )

        # 触发长期记忆更新
        update_long_term_memory.delay(user_id, "manual_logout")
        # update_long_term_memory(user_id, "daily_update")
        return jsonify(msg="Successfully logged out")
    except Exception as e:
        logger.error(f"退出登录失败: {str(e)}", exc_info=True)
        return (
            jsonify({"status": "error", "message": "退出登录时发生错误，请稍后重试"}),
            500,
        )


"""400请求参数错误"""


@app.errorhandler(400)
def handle_bad_request(e):
    logger.error(f"请求参数错误: {str(e)}")
    return jsonify(code=400, msg="请求参数错误"), 400


"""429请求过于频繁"""


@app.errorhandler(429)
def rate_limit_exceeded(e):
    logger.error(f"请求过于频繁: {str(e)}")
    return jsonify(code=429, msg="操作过于频繁"), 429


"""刷新令牌"""


# -------------------------------------  #
# 在请求时将access_token换成refresh_token #
# -------------------------------------  #
@app.route("/api/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh_token():
    try:
        current_user = get_jwt_identity()
        redis_client.setex(
            f"user:active:{current_user}", 1800, datetime.now().isoformat()
        )
        new_access_token = create_access_token(identity=current_user)

        return jsonify(
            {
                "code": 200,
                "msg": "令牌刷新成功",
                "data": {"access_token": new_access_token},
            }
        )
    except Exception as e:
        logger.error(f"令牌刷新失败: {str(e)}")
        return jsonify({"code": 401, "msg": "刷新令牌无效或已过期"}), 401


####################
# 注册模块
####################
"""发送手机验证码（用户注册）"""


@app.route("/api/register/send_sms", methods=["POST"])
@rate_limit("register_sms_rate", limit=3, period=60)  # 1分钟最多3次，可调整
def send_register_sms():
    phone = request.json.get("phone")

    # 1. 校验手机号格式（中国大陆）
    if not re.match(r"^1[3-9]\d{9}$", phone):
        return jsonify(code=400, msg="手机号格式错误"), 400

    # 2. 检查手机号是否已注册
    if User.query.filter_by(phone=phone).first():
        return jsonify(code=400, msg="手机号已注册"), 400

    # 3. 生成并存储验证码（生产环境用随机，这里先保持测试值，记得改）
    # code = str(random.randint(100000, 999999))
    code = "666666"
    redis_client.setex(f"sms_code:{phone}", 300, code)

    # 4. 异步发送短信（Celery任务）
    async_send_sms.delay(phone, code)

    return jsonify(code=200, msg="验证码已发送")


"""发送邮箱验证码（用户注册）"""


@app.route("/api/register/send_email", methods=["POST"])
@rate_limit("register_email_rate", limit=3, period=60)  # 1分钟最多3次，可调整
def send_register_email():
    email = request.json.get("email")

    # 1. 校验邮箱格式
    if not re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
        return jsonify(code=400, msg="邮箱格式错误"), 400

    # 2. 检查邮箱是否已注册
    if User.query.filter_by(email=email).first():
        return jsonify(code=400, msg="邮箱已注册"), 400

    # 3. 生成并存储验证码（生产环境用随机，这里先保持测试值，记得改）
    # code = str(random.randint(100000, 999999))
    code = "888888"
    redis_client.setex(f"email_code:{email}", 300, code)

    # 4. 异步发送邮件（Celery任务）
    async_send_email.delay(email, code)

    return jsonify(code=200, msg="验证码已发送")


"""提交注册信息"""


@app.route("/api/register/submit", methods=["POST"])
def register_submit():
    data = request.json
    phone = data.get("phone")
    sms_code = data.get("sms_code")
    email = data.get("email")
    email_code = data.get("email_code")
    password = data.get("password")
    confirm_password = data.get("confirm_password")

    # 1. 校验手机格式
    if not re.match(r"^1[3-9]\d{9}$", phone):
        return jsonify(code=400, msg="手机号格式错误"), 400

    # 2. 校验手机验证码
    stored_sms_code = redis_client.get(f"sms_code:{phone}")
    logger.info(
        "手机号{} 验证码对比: stored_sms_code={}, sms_code={}".format(
            phone, stored_sms_code, sms_code
        )
    )
    if not stored_sms_code or stored_sms_code.decode() != sms_code:
        return jsonify(code=400, msg="短信验证码错误"), 400

    # 3. 校验邮箱格式（原正则可简化，和 send_register_email 保持一致逻辑）
    if not re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
        return jsonify(code=400, msg="邮箱格式错误"), 400

    # 4. 校验邮箱验证码
    stored_email_code = redis_client.get(f"email_code:{email}")
    if not stored_email_code or stored_email_code.decode() != email_code:
        return jsonify(code=400, msg="邮箱验证码错误"), 400

    # 5. 校验密码一致性
    if password != confirm_password:
        return jsonify(code=400, msg="两次输入密码不一致"), 400

    # 6. 校验密码复杂度（至少8位，含字母和数字）
    if not re.match(r"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$", password):
        return jsonify(code=400, msg="密码需至少8位且包含字母和数字"), 400

    # 7. 创建用户
    password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode("utf-8")
    logger.info("手机号{} 密码哈希: password_hash={}".format(phone, password_hash))
    user = User(
        phone=phone,
        password_hash=password_hash,
        email=email,
    )
    db.session.add(user)
    try:
        db.session.flush()
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        logger.error(f"用户创建提交失败: {str(e)}", exc_info=True)
        return jsonify(code=500, msg="注册失败，数据库操作异常"), 500

    # 8. 调用内存系统创建用户数据（确保 mindcare_system 定义和方法正确）
    try:
        mindcare_system["memory_system"]._create_user_data(user_id=user.id)
    except KeyError as e:
        logger.error(f"内存系统调用失败，缺少对应键: {str(e)}", exc_info=True)
    except Exception as e:
        logger.error(f"内存系统创建用户数据失败: {str(e)}", exc_info=True)

    # 9. 生成JWT令牌（自动登录）
    access_token = create_access_token(identity=user.id)
    refresh_token = create_refresh_token(identity=user.id)

    # 10. 清理验证码
    redis_client.delete(f"sms_code:{phone}", f"email_code:{email}")

    return jsonify(
        code=200,
        msg="注册成功",
        data={"access_token": access_token, "refresh_token": refresh_token},
    )


####################
# 用户档案模块
####################
"""获取用户信息"""


@app.route("/api/profile", methods=["GET"])
@jwt_required()
def get_profile():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    logger.info("用户id{}".format(user_id))  # str.format()
    # 查询用户档案
    user = db.session.get(User, user_id)
    if not user:
        return jsonify(code=404, msg="用户不存在"), 404

    # 构造返回数据
    profile_data = {
        "avatar_url": user.avatar_url or "default_avatar.png",
        "nickname": user.nickname or "",
        "gender": user.gender or "",
        "signature": user.signature or "这个人很懒，什么都没留下。",
    }

    return jsonify(code=200, data=profile_data)


"""更新用户信息"""


@app.route("/api/profile", methods=["PUT"])
@jwt_required()
def update_profile():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    data = request.json

    # 数据验证
    if not all(key in ["nickname", "gender", "signature"] for key in data.keys()):
        return jsonify(code=400, msg="非法字段"), 400

    # 更新数据库
    try:
        user = User.query.get(user_id)
        for key, value in data.items():
            setattr(user, key, value.strip() if isinstance(value, str) else value)
        db.session.commit()
        return jsonify(code=200, msg="保存成功")
    except Exception as e:
        db.session.rollback()
        return jsonify(code=500, msg="保存失败"), 500


"""上传头像接口"""
UPLOAD_FOLDER = "static/avatar"
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)


def allowed_file(filename):
    ALLOWED_EXTENSIONS = {"jpg", "png"}
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/api/upload_avatar", methods=["POST"])
@jwt_required()
def upload_avatar():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    file = request.files.get("avatar")

    # 文件校验
    if not file or file.filename == "":
        return jsonify(code=400, msg="未选择文件"), 400
    if not allowed_file(file.filename):
        return jsonify(code=400, msg="仅支持JPG/PNG格式"), 400

    # 生成文件名
    filename = f"{user_id}.jpg"
    # 构建头像路径
    avatar_url = f"{UPLOAD_FOLDER}/{filename}"
    file.save(avatar_url)

    # 更新 user_profile 表
    user = db.session.get(User, user_id)
    # user = User.query.filter_by(id=user_id).first()
    if not user:
        return jsonify(code=404, msg="未找到用户资料记录，无法更新头像路径"), 404
    user.avatar_url = avatar_url
    try:
        db.session.commit()
        return jsonify(
            code=200,
            data={"avatar_url": avatar_url},
            msg="头像上传成功，用户资料已更新",
        )
    except Exception as e:
        db.session.rollback()
        return jsonify(code=500, msg=f"更新用户资料时出错: {str(e)}"), 500


####################
# 情绪记录模块
####################
"""情绪记录"""
from utils.emotion import emotion_color_map, generate_prompt


@app.route("/api/emotion/upload", methods=["POST"])
@jwt_required()
def upload_emotion():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    data = request.json or {}

    emotion = data.get("emotion") or ""
    content = data.get("content")
    if not content:
        return jsonify(code=400, msg="记录内容不能为空"), 400

    prompt = generate_prompt(emotion, content)

    ai_feedback = mindcare_system["llm_service"].generate_response(
        prompt=prompt,
        system_message="你是一个情绪记录辅助AI助手",
        max_tokens=500,
        temperature=0.5,
    )
    logger.info(f"ai反馈{ai_feedback}")
    ai_feedback = json.loads(ai_feedback)
    summary = ai_feedback.get("summary") or ""
    ai_response = ai_feedback.get("response") or ""
    new_record = EmotionRecord(
        user_id=user_id,
        emotion=emotion,
        content=content,
        summary=summary,
        ai_response=ai_response,
        image_url=data.get("image_url") or "",
    )

    try:
        db.session.add(new_record)
        db.session.commit()
        return jsonify(
            code=200,
            msg="情绪记录上传成功",
            data={"id": new_record.id, "summary": summary, "ai_response": ai_response},
        )

    except Exception as e:
        db.session.rollback()
        logger.error(f"情绪记录上传失败: {str(e)}", exc_info=True)
        return jsonify(code=500, msg="上传情绪记录时发生内部错误"), 500


@app.route("/api/emotion/conversation", methods=["POST"])
@jwt_required()
def upload_emotion_conversation():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    conversation_id = request.json.get("conversation_id")
    user_buffer = mindcare_system.get("memory_system").get_user_buffer(conversation_id)

    user_messages = [
        msg["content"]
        for msg in user_buffer
        if isinstance(msg, dict) and msg.get("role") == "user" and msg.get("content")
    ]
    if not user_messages:
        return jsonify({"status": "error", "message": "对话中无用户发言"}), 400
    logger.info(user_messages)
    # 5. 构造大模型prompt，分析整体对话情绪+生成结果
    # 格式示例：将用户发言拼接为自然语言文本
    user_dialogue = "\n".join([f"用户：{msg}" for msg in user_messages])
    logger.info(type(user_dialogue))
    prompt = f"""
任务：分析用户以下对话的整体情绪，并生成标题、总结和待反馈建议。

用户对话历史：
{user_dialogue}
请严格只输出如下JSON，不要输出任何解释或多余内容：
{{
    "emotion": "情绪标签（从[积极、消极、中性、焦虑、抑郁、愤怒、喜悦、疲惫]中选择1-2个）",
    "summary": "标题（10字以内，概括对话核心）",
    "content": "内容总结（50字以内，概括用户主要表达）",
     "feedback": "待反馈建议（对用户的情绪支持或行动建议，50字以内）"
}}
"""
    logger.info(f"生成的prompt: {prompt}")




    # 6. 调用大模型生成结果（使用mindcare_system中的llm_service）
    try:
        llm_response = mindcare_system["llm_service"].generate_response_with_rag(
            prompt=prompt,
            system_message="你是一个情绪记录辅助AI助手，需基于用户完整对话分析整体情绪，并生成标题、总结和待反馈建议。",
            model_key="Qwen/Qwen2.5-7B-Instruct",
            max_tokens=500,
            temperature=0.5,
        )
        logger.info(f"大模型返回结果: {llm_response}")

        # 清理大模型返回的JSON内容，去掉可能的```json```代码块标记
        clean_response = llm_response.strip() if llm_response else ""
        if clean_response.startswith("```json"):
            clean_response = clean_response[7:]  # 去掉```json
        if clean_response.endswith("```"):
            clean_response = clean_response[:-3]  # 去掉```
        clean_response = clean_response.strip()

        
        # 清理大模型返回的JSON内容，去掉可能的```json```代码块标记
        clean_response = llm_response.strip() if llm_response else ""
        if clean_response.startswith('```json'):
            clean_response = clean_response[7:]  # 去掉```json
        if clean_response.endswith('```'):
            clean_response = clean_response[:-3]  # 去掉```
        clean_response = clean_response.strip()
        
        # 检查清理后的响应是否为空
        if not clean_response:
            logger.error("大模型返回的内容为空")
            return jsonify({"status": "error", "message": "大模型返回内容为空"}), 500

        # 解析大模型返回的JSON
        llm_result = json.loads(clean_response)
        # 校验返回字段是否完整
        required_fields = ["emotion", "summary", "content", "feedback"]
        if not all(field in llm_result for field in required_fields):
            return (
                jsonify({"status": "error", "message": "大模型返回格式缺失字段"}),
                500,
            )
    except json.JSONDecodeError as e:
        logger.error(f"大模型返回内容无法解析为JSON: {llm_response}", exc_info=True)
        logger.error(f"JSON解析错误详情: {str(e)}")
        return jsonify({"status": "error", "message": "大模型返回非JSON格式"}), 500
    except Exception as e:
        logger.error(f"大模型调用失败: {str(e)}", exc_info=True)
        return (
            jsonify(
                {"status": "error", "message": "大模型调用失败", "details": str(e)}
            ),
            500,
        )

    # 7. 存储结果到情绪记录表
    try:
        # 检查是否已存在该对话的情绪记录（避免重复）
        existing_record = EmotionRecord.query.filter_by(
            user_id=user_id, conversation_id=conversation_id
        ).first()
        if existing_record:
            return (
                jsonify(
                    {
                        "status": "warning",
                        "message": "该对话已记录情绪",
                        "data": {
                            "record_id": existing_record.id,
                            "emotion": existing_record.emotion,
                        },
                    }
                ),
                200,
            )  # 已存在时返回200而非409，更友好

        # 创建新记录（假设EmotionRecord模型包含以下字段）
        new_record = EmotionRecord(
            user_id=user_id,
            conversation_id=conversation_id,
            emotion=llm_result["emotion"],
            summary=llm_result["summary"],
            content=llm_result["content"],
            ai_response=llm_result["feedback"],
            # raw_dialogue=user_buffer,  # 存储原始对话（含user/assistant）
        )
        db.session.add(new_record)
        db.session.commit()
    except SQLAlchemyError as e:
        db.session.rollback()
        return (
            jsonify(
                {"status": "error", "message": "情绪记录存储失败", "details": str(e)}
            ),
            500,
        )

    # 8. 返回成功响应给前端
    return (
        jsonify(
            {
                "status": "success",
                "message": "情绪记录成功",
                "data": {
                    "record_id": new_record.id,
                    "conversation_id": conversation_id,
                    "emotion": llm_result["emotion"],
                    "summary": llm_result["summary"],
                    "content": llm_result["content"],
                    "ai_response": llm_result["feedback"],
                    "created_at": new_record.created_at.isoformat(),
                },
            }
        ),
        201,
    )  # 201表示资源创建成功


# 2.0获取情绪历史记录（根据年和月）
@app.route("/api/emotion/querybymonth", methods=["GET"])
@jwt_required()
def get_emotion_records_by_month():
    """
    获取所有的数据
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    year = request.args.get("year", type=int)
    month = request.args.get("month", type=int)

    # 参数验证
    if not user_id:
        return jsonify({"error": "用户不存在"}), 400
    if not (year and month):
        return jsonify({"error": "year and month are required"}), 400
    if not (1 <= month <= 12):
        return jsonify({"error": "month must be between 1 and 12"}), 400

    # 构建查询条件（使用UTC时区）
    start_date = datetime(year, month, 1, tzinfo=timezone.utc)
    end_date = (
        datetime(year, month + 1, 1, tzinfo=timezone.utc)
        if month < 12
        else datetime(year + 1, 1, 1, tzinfo=timezone.utc)
    )


    # 查询数据库
    records = (
        EmotionRecord.query.filter(
            EmotionRecord.user_id == user_id,
            EmotionRecord.created_at >= start_date,
            EmotionRecord.created_at < end_date,
        )
        .order_by(EmotionRecord.created_at.desc())
        .all()
    )

    # 转换为JSON格式
    result = [
        {
            "id": record.id,
            "emotion": record.emotion,
            "content": record.content,
            "summary": record.summary,
            "created_at": record.created_at.isoformat(),
            "ai_response": record.ai_response,
        }
        for record in records
    ]

    return jsonify({"status": "success", "data": result, "count": len(result)}), 200


# 2.1获取情绪历史记录月历（根据年和月）
@app.route("/api/emotion/calendar", methods=["GET"])
@jwt_required()
def get_emotion_calendar_by_month():
    """
    获取颜色
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    year = request.args.get("year", type=int)
    month = request.args.get("month", type=int)

    # 参数验证
    if not user_id:
        return jsonify({"code": 400, "msg": "用户不存在"}), 400
    if not (year and month):
        return jsonify({"code": 400, "msg": "year and month are required"}), 400
    if not (1 <= month <= 12):
        return jsonify({"code": 400, "msg": "month must be between 1 and 12"}), 400

    # 确定当月的日期范围（使用UTC时区）
    _, last_day_of_month = calendar.monthrange(year, month)
    start_date = datetime(year, month, 1, tzinfo=timezone.utc)
    end_date = datetime(year, month, last_day_of_month, 23, 59, 59, tzinfo=timezone.utc)

    # 查询该用户当月的所有情绪记录
    records = (
        EmotionRecord.query.filter(
            and_(
                EmotionRecord.user_id == user_id,
                EmotionRecord.created_at >= start_date,
                EmotionRecord.created_at <= end_date,
            )
        )
        .order_by(EmotionRecord.created_at.desc())
        .all()
    )

    # 按日期分组整理记录
    daily_records = {}
    for record in records:
        # 修复时区问题：将UTC时间转换为本地时间再获取日期
        # 假设本地时区为UTC+8（中国标准时间）
        local_time = record.created_at.replace(tzinfo=timezone.utc).astimezone(
            timezone(timedelta(hours=8))
        )
        record_date = local_time.date().isoformat()
        if record_date not in daily_records:
            daily_records[record_date] = []
        daily_records[record_date].append(record)

    # 构建日历数据，遍历当月每一天
    calendar_data = []
    current_date = start_date.date()
    while current_date <= end_date.date():
        date_str = current_date.isoformat()
        item = {"date": date_str}
        if date_str in daily_records:
            # 处理当天有记录的情况，计算要显示的颜色
            emotions_of_day = daily_records[date_str]
            color_count = {}
            latest_record = None
            for rec in emotions_of_day:
                # 根据情绪找对应颜色，遍历映射表
                for color, emotions in emotion_color_map.items():
                    if rec.emotion in emotions:
                        # 统计颜色出现次数
                        color_count[color] = color_count.get(color, 0) + 1
                        # 更新最近记录（用于频率相同时）
                        if (
                            not latest_record
                            or rec.created_at > latest_record.created_at
                        ):
                            latest_record = rec
                        break
            # 找出出现频率最高的颜色
            max_count = -1
            selected_color = None
            for color, count in color_count.items():
                if count > max_count or (
                    count == max_count
                    and emotion_color_map[color].count(latest_record.emotion) > 0
                ):
                    max_count = count
                    selected_color = color
            item["emotion-colour"] = selected_color
        else:
            # 当天无记录，用灰色
            item["emotion-colour"] = "灰"
        calendar_data.append(item)
        # 日期加1，继续循环
        current_date += timedelta(days=1)

    return jsonify({"code": 200, "data": {"calendar": calendar_data}}), 200


# 3.获取情绪历史记录（根据日期）


@app.route("/api/emotion/querybydate", methods=["GET"])
@jwt_required()
def get_emotion_records_by_date():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )
    date_str = request.args.get("date")

    # 参数验证
    if not user_id:
        return jsonify({"code": 400, "msg": "用户不存在"}), 400
    if not date_str:
        return jsonify({"code": 400, "msg": "date参数是必需的"}), 400

    try:
        query_date = datetime.strptime(date_str, "%Y-%m-%d").date()
    except ValueError:
        return jsonify({"code": 400, "msg": "日期格式错误，正确格式: YYYY-MM-DD"}), 400

    # 构建查询条件（包含当天23:59:59，使用本地时区）
    # 将查询日期转换为UTC时间范围
    local_start = datetime.combine(query_date, datetime_time.min)
    local_end = datetime.combine(query_date, datetime_time.max)


    # 转换为UTC时间（本地时间+8小时 = UTC时间）
    start_time = local_start.replace(tzinfo=timezone(timedelta(hours=8))).astimezone(
        timezone.utc
    )
    end_time = local_end.replace(tzinfo=timezone(timedelta(hours=8))).astimezone(
        timezone.utc
    )


    # 查询数据库
    records = (
        EmotionRecord.query.filter(
            EmotionRecord.user_id == user_id,
            EmotionRecord.created_at >= start_time,
            EmotionRecord.created_at <= end_time,
        )
        .order_by(EmotionRecord.created_at.desc())
        .all()
    )

    # 为每条记录添加颜色字段
    result = []
    for record in records:
        # 查找情绪对应的颜色（假设 emotion_color_map 已定义）
        color = None
        for color_name, emotions in emotion_color_map.items():
            if record.emotion in emotions:
                color = color_name
                break
        result.append(
            {
                "id": record.id,
                "emotion": record.emotion,
                "content": record.content,
                "summary": record.summary,
                "created_at": record.created_at.isoformat(),
                "ai_response": record.ai_response,
                "color": color or "灰色",
            }
        )

    return jsonify({"code": 200, "data": result, "count": len(result)}), 200


# 4. 获取情绪历史记录（分页）
@app.route("/api/emotion/querybypage", methods=["GET"])
@jwt_required()
def get_emotion_records_by_page():
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    page = request.args.get("page", 1, type=int)
    per_page = 5

    # 查询并分页，添加 error_out=False 参数
    records = (
        EmotionRecord.query.filter_by(user_id=user_id)
        .order_by(EmotionRecord.created_at.desc())
        .paginate(page=page, per_page=per_page, error_out=False)  # 关键修改点
    )

    # 构造返回数据
    data = [
        {
            "id": r.id,
            "emotion": r.emotion,
            "content": r.content,
            "created_at": r.created_at.isoformat(),
            "summary": r.summary,
            "ai_response": r.ai_response,
            "image_url": r.image_url,
        }
        for r in records.items
    ]

    # 建议：返回分页元数据，帮助前端判断
    return jsonify(
        code=200,
        data=data,
        page=records.page,
        per_page=records.per_page,
        total_pages=records.pages,  # 总页数
        total_items=records.total,  # 总记录数
    )


# 5. 获取记录详情
@app.route("/api/emotion/querybyid", methods=["GET"])
@jwt_required()
def get_emotion_record_by_id():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    record_id = request.args.get("record_id", type=int)
    record = EmotionRecord.query.filter_by(id=record_id, user_id=user_id).first()

    if not record:
        return jsonify(code=404, msg="记录不存在"), 404

    return jsonify(
        code=200,
        data={
            "id": record.id,
            "emotion": record.emotion,
            "content": record.content,
            "created_at": record.created_at.isoformat(),
            "summary": record.summary,
            "ai_response": record.ai_response,
            "image_url": record.image_url,
        },
    )


# 6. 编辑(更新)/删除记录
@app.route("/api/emotion/update", methods=["PUT", "DELETE"])
@jwt_required()
def update_emotion():
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    record_id = request.args.get("record_id", type=int)

    record = EmotionRecord.query.filter_by(id=record_id, user_id=user_id).first()

    if not record:
        return jsonify(code=404, msg="记录不存在"), 404

    if request.method == "PUT":
        data = request.json or {}
        # 更新各个字段，如有对应字段就更新，否则保持原值
        if "emotion" in data:
            record.emotion = data.get("emotion")
        if "content" in data:
            record.content = data.get("content")
        if "image_url" in data:
            record.image_url = data.get("image_url")

        prompt = generate_prompt(record.emotion, record.content)

        ai_feedback = mindcare_system["llm_service"].generate_response(
            prompt=prompt,
            system_message="你是一个情绪记录辅助AI助手",
            max_tokens=500,
            temperature=0.5,
        )
        logger.info(f"ai反馈{ai_feedback}")
        ai_feedback = json.loads(ai_feedback)
        record.summary = ai_feedback.get("summary") or ""
        record.ai_response = ai_feedback.get("response") or ""
        try:
            db.session.commit()
            return jsonify(code=200, msg="更新成功")
        except Exception as e:
            db.session.rollback()
            logger.error(f"更新记录失败: {str(e)}", exc_info=True)
            return jsonify(code=500, msg="更新记录时发生内部错误"), 500
        print("")
    elif request.method == "DELETE":
        db.session.delete(record)
        db.session.commit()

    return jsonify(code=200, msg="操作成功")


####################
# 心理测试模块
####################
"""
# MBTI 人格测试接口
"""


@app.route("/api/test/mbti", methods=["POST"])
@jwt_required()
def mbti_test():
    """
    # MBTI测试接口：
    # - 请求体：{"answers": {1: "A", 2: "B", ..., 93: "A"}} （必须包含1-93题）
    # - 返回：MBTI类型及描述
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )  # 前端需要跳转至登录页
    data = request.json
    answers = data.get("answers")

    # 基本校验
    if not answers or len(answers) != 93:
        return jsonify({"code": 400, "msg": "需提交1-93题完整答案"}), 400

    # 计算MBTI
    try:
        mbti_type = calculate_mbti(answers)
        description = MBTI_DESCRIPTIONS.get(mbti_type, "暂未完善描述")

        new_record = PsychologicalAssessmentRecord(
            user_id=user_id,
            assessment_name="MBTI",
            results=json.dumps({"mbti_type": mbti_type, "description": description}),
            ai_response="",
            assessment_time=datetime.now(timezone.utc),
        )
        db.session.add(new_record)
        db.session.commit()
        return jsonify(
            {"code": 200, "data": {"mbti_type": mbti_type, "description": description}}
        )
    except Exception as e:
        return jsonify({"code": 500, "msg": f"计算失败：{str(e)}"}), 500


"""
SDS 抑郁自评量表计算接口
"""


@app.route("/api/test/sds", methods=["POST"])
@jwt_required()
def calculate_sds():
    """
    SDS抑郁自评量表计算接口
    请求体格式: {"answers": {1: 1, 2: 2, ..., 20: 4}}
    选项说明: 1="没有或很少时间", 2="小部分时间", 3="相当多时间", 4="绝大部分或全部时间"
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )
    try:
        # 获取请求数据
        data = request.json
        answers = data.get("answers")
        logger.info(f"用户 {user_id} 提交的SDS答案: {answers}")

        # 验证数据完整性
        """
        if not answers or len(answers) != 20:
            return (
                jsonify({
                    "code": 400,
                    "msg": "请提供20道题的完整答案",
                    "data": None
                }),
                400,
            )

        # 验证题号范围
        if set(answers.keys()) != set(range(1, 21)):
            return (
                jsonify({
                    "code": 400,
                    "msg": "题目编号必须为1-20",
                    "data": None
                }),
                400,
            )

        # 验证选项有效性
        for q_num, score in answers.items():
            if score not in {1, 2, 3, 4}:
                return (
                    jsonify({
                        "code": 400,
                        "msg": f"题目{q_num}的选项只能是1-4",
                        "data": None,
                    }),
                    400,
                )
        """
        # 计算粗分
        raw_score = 0
        for q_num, score in answers.items():
            if q_num in REVERSE_QUESTIONS_SDS:
                # 反向计分题：4→1, 3→2, 2→3, 1→4
                raw_score += 5 - score
            else:
                # 正向计分题
                raw_score += score

        # 计算标准分
        standard_score = int(raw_score * 1.25)

        # 判断抑郁程度
        depression_level = get_sds_depression_level(standard_score)
        new_record = PsychologicalAssessmentRecord(
            user_id=user_id,
            assessment_name="SDS",
            results=json.dumps(
                {
                    "raw_score": raw_score,  # 粗分
                    "standard_score": standard_score,  # 标准分
                    "depression_level": depression_level,  # 抑郁程度
                    "interpretation": get_sds_interpretation(
                        standard_score
                    ),  # 结果解释
                }
            ),
            ai_response="",
            assessment_time=datetime.now(timezone.utc),
        )
        db.session.add(new_record)
        db.session.commit()

        # 返回结果
        return (
            jsonify(
                {
                    "code": 200,
                    "msg": "计算成功",
                    "data": {
                        "raw_score": raw_score,  # 粗分
                        "standard_score": standard_score,  # 标准分
                        "depression_level": depression_level,  # 抑郁程度
                        "interpretation": get_sds_interpretation(
                            standard_score
                        ),  # 结果解释
                    },
                }
            ),
            200,
        )

    except Exception as e:
        return (
            jsonify(
                {"code": 500, "msg": f"计算过程中出现错误: {str(e)}", "data": None}
            ),
            500,
        )


"""
SAS 抑郁自评量表计算接口
"""


@app.route("/api/test/sas", methods=["POST"])
@jwt_required()
def calculate_sas():
    """
    SAS焦虑自评量表计算接口
    请求体格式: {"answers": {1: 1, 2: 2, ..., 20: 4}}
    选项说明: 1="没有或很少时间", 2="小部分时间", 3="相当多时间", 4="绝大部分或全部时间"
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )
    try:
        # 获取请求数据
        data = request.json
        answers = data.get("answers")
        logger.info(f"用户 {user_id} 提交的SAS答案: {answers}")

        # 计算粗分
        raw_score = 0
        for q_num, score in answers.items():
            if q_num in REVERSE_QUESTIONS_SAS:
                # 反向计分题：4→1, 3→2, 2→3, 1→4
                raw_score += 5 - score
            else:
                # 正向计分题
                raw_score += score

        # 计算标准分
        standard_score = int(raw_score * 1.25)

        # 判断焦虑程度
        anxiety_level = get_anxiety_level(standard_score)

        # 保存评估记录
        new_record = PsychologicalAssessmentRecord(
            user_id=user_id,
            assessment_name="SAS",
            results=json.dumps(
                {
                    "raw_score": raw_score,
                    "standard_score": standard_score,
                    "anxiety_level": anxiety_level,
                    "interpretation": get_sas_interpretation(standard_score),
                }
            ),
            ai_response="",
            assessment_time=datetime.now(),
        )
        db.session.add(new_record)
        db.session.commit()

        # 返回结果
        return (
            jsonify(
                {
                    "code": 200,
                    "msg": "计算成功",
                    "data": {
                        "raw_score": raw_score,
                        "standard_score": standard_score,
                        "anxiety_level": anxiety_level,
                        "interpretation": get_sas_interpretation(standard_score),
                    },
                }
            ),
            200,
        )

    except Exception as e:
        db.session.rollback()
        logger.error(f"SAS计算错误: {str(e)}", exc_info=True)
        return (
            jsonify(
                {"code": 500, "msg": f"计算过程中出现错误: {str(e)}", "data": None}
            ),
            500,
        )


"""
贝克测试计算接口
"""


@app.route("/api/test/baker", methods=["POST"])
@jwt_required()
def baker_depression_test():
    """
    贝克抑郁心理测试接口
    请求体格式: {"answers": {1: 0, 2: 1, ..., 21: 3}}
    选项说明: 0-3分，对应题目中的四个选项
    """
    user_id = get_jwt_identity()
    if not user_id:
        return (
            jsonify({"status": "error", "message": "请先登录"}),
            401,
        )
    try:
        # 获取请求数据
        data = request.json
        answers = data.get("answers")
        logger.info(f"用户 {user_id} 提交的贝克抑郁测试答案: {answers}")

        # 计算总分
        total_score = sum(answers.values())

        # 判断抑郁程度
        depression_level = get_baker_depression_level(total_score)

        # 保存评估记录
        new_record = PsychologicalAssessmentRecord(
            user_id=user_id,
            assessment_name="贝克抑郁测试",
            results=json.dumps(
                {
                    "total_score": total_score,
                    "depression_level": depression_level,
                    "interpretation": get_baker_interpretation(total_score),
                }
            ),
            ai_response="",
            assessment_time=datetime.now(),
        )
        db.session.add(new_record)
        db.session.commit()

        # 返回结果
        return (
            jsonify(
                {
                    "code": 200,
                    "msg": "测试完成，计算成功",
                    "data": {
                        "total_score": total_score,
                        "depression_level": depression_level,
                        "interpretation": get_baker_interpretation(total_score),
                    },
                }
            ),
            200,
        )

    except Exception as e:
        db.session.rollback()
        logger.error(f"贝克抑郁测试计算错误: {str(e)}", exc_info=True)
        return (
            jsonify(
                {"code": 500, "msg": f"计算过程中出现错误: {str(e)}", "data": None}
            ),
            500,
        )


####################
# 心理测试记录查询  #
####################


@app.route("/api/assessment/record/<int:record_id>", methods=["GET"])
@jwt_required()
def get_assessment_record_by_id(record_id):
    """根据ID查询心理测试记录（整数ID）"""
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    try:
        # 查询记录并验证用户权限
        record = PsychologicalAssessmentRecord.query.filter_by(
            id=record_id, user_id=user_id
        ).first()
        logger.info(f"查询记录ID: {record_id}，用户ID: {user_id}")
        logger.info(f"查询记录结果: {record}")
        if not record:
            return jsonify({"code": 404, "msg": "记录不存在"}), 404

        # 序列化结果
        result = {
            "id": record.id,
            "user_id": str(record.user_id),
            "assessment_name": record.assessment_name,
            "results": json.loads(record.results),
            "ai_response": record.ai_response,
            "assessment_time": record.assessment_time.isoformat(),
        }

        return jsonify({"code": 200, "data": result}), 200

    except Exception as e:
        return jsonify({"code": 500, "msg": f"查询记录失败: {str(e)}"}), 500


@app.route("/api/assessment/record/date", methods=["GET"])
@jwt_required()
def get_assessment_records_by_date():
    """按日期查询心理测试记录"""
    # 代码与之前一致，无需修改ID相关逻辑
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    date_str = request.args.get("date")
    if not date_str:
        return jsonify({"code": 400, "msg": "date参数是必需的"}), 400

    try:
        query_date = datetime.strptime(date_str, "%Y-%m-%d").date()
        start_time = datetime.combine(query_date, datetime.min.time())
        end_time = datetime.combine(query_date, datetime.max.time())

        records = (
            PsychologicalAssessmentRecord.query.filter(
                and_(
                    PsychologicalAssessmentRecord.user_id == user_id,
                    PsychologicalAssessmentRecord.assessment_time >= start_time,
                    PsychologicalAssessmentRecord.assessment_time <= end_time,
                )
            )
            .order_by(PsychologicalAssessmentRecord.assessment_time.desc())
            .all()
        )

        result = []
        for record in records:
            result.append(
                {
                    "id": record.id,
                    "assessment_name": record.assessment_name,
                    "results": json.loads(record.results),
                    "assessment_time": record.assessment_time.isoformat(),
                }
            )

        return jsonify({"code": 200, "data": result, "count": len(result)}), 200

    except ValueError:
        return jsonify({"code": 400, "msg": "日期格式错误，正确格式: YYYY-MM-DD"}), 400
    except Exception as e:
        return jsonify({"code": 500, "msg": f"查询记录失败: {str(e)}"}), 500


@app.route("/api/assessment/record/month", methods=["GET"])
@jwt_required()
def get_assessment_records_by_month():
    """按年月查询心理测试记录"""
    # 代码与之前一致，无需修改ID相关逻辑
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    year = request.args.get("year", type=int)
    month = request.args.get("month", type=int)

    if not (year and month):
        return jsonify({"code": 400, "msg": "year和month参数是必需的"}), 400
    if not (1 <= month <= 12):
        return jsonify({"code": 400, "msg": "month必须在1-12之间"}), 400

    try:
        start_date = datetime(year, month, 1)
        end_date = (
            datetime(year, month + 1, 1) if month < 12 else datetime(year + 1, 1, 1)
        )

        records = (
            PsychologicalAssessmentRecord.query.filter(
                and_(
                    PsychologicalAssessmentRecord.user_id == user_id,
                    PsychologicalAssessmentRecord.assessment_time >= start_date,
                    PsychologicalAssessmentRecord.assessment_time < end_date,
                )
            )
            .order_by(PsychologicalAssessmentRecord.assessment_time.desc())
            .all()
        )

        result = []
        for record in records:
            result.append(
                {
                    "id": record.id,
                    "assessment_name": record.assessment_name,
                    "results": json.loads(record.results),
                    "assessment_time": record.assessment_time.isoformat(),
                }
            )

        return jsonify({"code": 200, "data": result, "count": len(result)}), 200

    except Exception as e:
        return jsonify({"code": 500, "msg": f"查询记录失败: {str(e)}"}), 500


@app.route("/api/assessment/records", methods=["GET"])
@jwt_required()
def get_assessment_records_by_page():
    """心理测试记录分页查询"""
    # 代码与之前一致，无需修改ID相关逻辑
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    page = request.args.get("page", 1, type=int)
    per_page = request.args.get("per_page", 10, type=int)

    try:
        pagination = (
            PsychologicalAssessmentRecord.query.filter_by(user_id=user_id)
            .order_by(PsychologicalAssessmentRecord.assessment_time.desc())
            .paginate(page=page, per_page=per_page, error_out=False)
        )

        result = []
        for record in pagination.items:
            result.append(
                {
                    "id": record.id,
                    "assessment_name": record.assessment_name,
                    "results": json.loads(record.results),
                    "assessment_time": record.assessment_time.isoformat(),
                }
            )

        return (
            jsonify(
                {
                    "code": 200,
                    "data": result,
                    "count": pagination.total,
                    "total_pages": pagination.pages,
                    "current_page": pagination.page,
                }
            ),
            200,
        )

    except Exception as e:
        return jsonify({"code": 500, "msg": f"查询记录失败: {str(e)}"}), 500


@app.route("/api/assessment/record/<int:record_id>", methods=["DELETE"])
@jwt_required()
def delete_assessment_record(record_id):
    """根据ID删除心理测试记录（整数ID）"""
    user_id = get_jwt_identity()
    if not user_id:
        return jsonify({"status": "error", "message": "请先登录"}), 401

    try:
        # 查询记录并验证用户权限
        record = PsychologicalAssessmentRecord.query.filter_by(
            id=record_id, user_id=user_id
        ).first()

        if not record:
            return jsonify({"code": 404, "msg": "记录不存在"}), 404

        # 删除记录
        db.session.delete(record)
        db.session.commit()

        return jsonify({"code": 200, "msg": "记录删除成功"}), 200

    except Exception as e:
        db.session.rollback()
        return jsonify({"code": 500, "msg": f"删除记录失败: {str(e)}"}), 500


####################
# 主程序入口
####################
if __name__ == "__main__":
    # 从 SETTINGS 获取主机和端口
    host = SETTINGS.host
    print(host)
    port = SETTINGS.port
    debug_mode = SETTINGS.debug_mode
    logger.info(
        f"启动 Flask Web 应用，地址: http://{host}:{port}，调试模式: {debug_mode}"
    )
    # 注意：生产环境不应使用 debug=True

    app.run(host=host, port=port, debug=debug_mode)
    # app.run(host='0.0.0.0', port=5000, debug=False)  # 关键！必须设置host为0.0.0.0
